<template>
  <div class="toolbar">
    <ul class="flex toolbar-btns-list">
      <el-input
        size="small"
        v-model="nodeName"
        placeholder="请输入内容"
        style="height: 30px"
        clearable
        @clear="handleClearIcon"
      >
        <template #append>
          <el-button :icon="Search" @click="handleSearchIcon" />
        </template>
      </el-input>
      <li class="toolbar-btns" title="保存" @click="handleSave">
        <i class="iconfont icon_save_fill"></i>
      </li>
      <li
        class="toolbar-btns toolbar-layout"
        :class="layoutActive"
        title="布局"
        ref="buttonRef"
        v-click-outside="onClickOutside"
      >
        <i class="icon"></i>布局
      </li>
      <li class="toolbar-btns" title="清空" @click="handleClear">
        <i class="iconfont icon_delete_fill"></i>
      </li>
      <li
        class="toolbar-btns"
        title="撤销"
        :class="undoStackLen ? '' : 'disabled'"
        @click="handleUndo"
      >
        <i class="iconfont icon_chexiao_fill"></i>
      </li>
      <li
        class="toolbar-btns"
        :class="redoStackLen ? '' : 'disabled'"
        title="恢复"
        @click="handleRedo"
      >
        <i class="iconfont icon_chongzuo_fill"></i>
      </li>
      <li class="toolbar-btns" code="zoomOut" title="放大" @click="zoomOut">
        <i class="iconfont icon_input_add"></i>
      </li>
      <li class="toolbar-btns" code="zoomIn" title="缩小" @click="zoomIn">
        <i class="iconfont icon_input_deduct"></i>
      </li>
      <li
        class="toolbar-btns toolbar-zoomIn"
        title="导出"
        @click="handleExport"
      >
        <i class="iconfont icon_download_fill"></i>
      </li>

      <!-- 快捷键提示 -->
      <ElPopover
        ref="popoverInfoRef"
        trigger="click"
        placement="right"
        width="630"
        v-model:visible="popoverInfoVisible"
      >
        <div class="tip-div">
          <div class="item" v-for="(item, index) in keyboardInfo" :key="index">
            <span>{{ item.opration }}</span>
            <span>{{ item.name }}</span>
          </div>
        </div>
        <template #reference>
          <li>
            <img
              :src="infoImg"
              alt=""
              class="toolbar-btns"
              style="width: 22px; height: 22px"
            />
          </li>
        </template>
      </ElPopover>
    </ul>
    <!-- <el-button>Click me</el-button> -->

    <ElPopover
      ref="popoverRef"
      :virtual-ref="buttonRef"
      trigger="click"
      virtual-triggering
      placement="left"
      width="180"
      v-model:visible="popoverVisible"
    >
      <div class="layout-list">
        <div
          class="layout-item"
          :class="item.type"
          v-for="(item, index) in layoutList"
          :key="index"
          @click="changeLayout(item.type)"
        >
          <i class="icon"></i>
          <span class="text">{{ item.name }}</span>
        </div>
      </div>
    </ElPopover>
    <div class="toolbar-tooltip">
      鼠标双击画布空白处可新建概念。按住 Shift
      键的同时，鼠标点击一个对象移动出一条线，移动到目标概念可新建关系。
    </div>
  </div>
</template>

<script lang="ts" setup>
import { emitter } from "@/utils/mitt";
import { delVerifyApi, downloadDesignApi } from "@/api/buildKg/tology";
import exportFile from "@/utils/export";
import G6 from "@antv/g6";

defineOptions({
  name: "GraphBar",
});
import type { Graph } from "@antv/g6";
import type { ICombo } from "@antv/g6-core";
import { Search } from "@element-plus/icons-vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { setAllStateDark, clearAllStats } from "./common/index";
import { ClickOutside as vClickOutside } from "element-plus";
import { deelNode } from "./common/utils";
import { handleAllChoose, handleCopyObject } from "./common/menuEvent";
import infoImg from "@/assets/icons/icon_fill_keyboard@2x.png";

let nodeName = ref("");
const graphModule: Ref<Graph> = inject("graphModule");
//清空画布
const handleClear = async () => {
  const params = {
    codeList: "",
    type: 3,
  };
  const { code, data } = await delVerifyApi(params);
  if (code === 200 && data.length) {
    ElMessage.error(
      "该画布有知识无法进行删除,请在知识管理中批量删除知识后再进行操作"
    );
    return;
  }
  ElMessageBox.confirm("您正在进行清空画布操作，是否继续?", "提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    graphModule.value.clear();
  });
};
let undoStackLen = ref(0);
let redoStackLen = ref(0);
import { clone } from "@antv/util";
import { deelSubmitData } from "@/views/buildKg/ontology/common/utils";
import { graphSaveApi } from "@/api/buildKg/tology";

onMounted(() => {
  emitter.on("graphInit", (graph: Graph) => {
    bindUndoRedo();
  });
  emitter.on("changeBarLayout", (type) => {
    changeLayout(type);
  });
  window.addEventListener("beforeunload", _beforeunloadHandler);
  window.addEventListener("keydown", linsenKeyboard);
});

const keyboardInfo = [
  { opration: "ctrl+s", name: "保存" },
  { opration: "ctrl+e", name: "清空画布" },
  { opration: "ctrl+z", name: "撤销" },
  { opration: "ctrl+y", name: "恢复" },
  { opration: "ctrl+a", name: "全选" },
  { opration: "ctrl+c", name: "复制概念" },
  { opration: "ctrl+drag", name: "框选" },
  { opration: "double  click", name: "新建概念" },
  { opration: "shift click", name: "新建关系" },
  { opration: "delete", name: "删除画布选中的概念或关系" },
  { opration: "esc", name: "取消选择" },
];

/** 监听键盘事件，快捷键操作 */
const linsenKeyboard = (e) => {
  let isNeedPreventDefault = false;
  let keyCode = e.keyCode || e.which || e.charCode;
  let ctrlKey = e.ctrlKey;
  console.log(keyCode, ctrlKey);
  if (ctrlKey && keyCode == 83) {
    // ctrl+s保存
    handleSave();
    isNeedPreventDefault = true;
  } else if (ctrlKey && keyCode == 90) {
    //  ctrl+z撤销
    handleUndo();
    isNeedPreventDefault = true;
  } else if (ctrlKey && keyCode == 89) {
    //  ctrl+y 恢复
    handleRedo();
    isNeedPreventDefault = true;
  } else if (ctrlKey && keyCode == 65) {
    //  ctrl+a 全选
    handleAllChoose();
    isNeedPreventDefault = true;
  } else if (ctrlKey && keyCode == 69) {
    handleClear();
    //  ctrl+e 清空画布
    isNeedPreventDefault = true;
  } else if (ctrlKey && keyCode == 67) {
    //  ctrl+c 复制概念
    const allSelectNode = graphModule.value.findAllByState("node", "selected"); // 获取所有选中状态的节点
    const allHightNode = graphModule.value.findAllByState("node", "highlight"); // 获取所有高亮状态的节点
    [...allHightNode, ...allSelectNode].forEach((item) => {
      handleCopyObject({ item: item });
    });
    isNeedPreventDefault = true;
  } else if (keyCode == 27) {
    //  esc 取消选择
    clearAllStats();
    isNeedPreventDefault = true;
  } else if (keyCode == 46) {
    //  delete 删除选中
    let allSelectNode = graphModule.value.findAllByState("node", "selected"); // 获取所有选中状态的节点
    let allHightNod = graphModule.value.findAllByState("node", "highlight"); // 获取所有高亮状态的节点
    let codeList = [];
    [...allSelectNode, ...allHightNod].forEach((item: any) => {
      let currentNode = item.getModel();
      codeList.push(currentNode.code);
    });
    if (!codeList.length) return;
    const params = {
      codeList: codeList.join(",") || "",
      type: 1,
    };
    delVerifyApi(params).then((res) => {
      if (res.code === 200 && res.data.length) {
        ElMessage.error(
          "该对象下有知识无法进行删除,请在知识管理中批量删除该对象下的知识后再进行操作"
        );
        return;
      } else {
        [...allSelectNode, ...allHightNod].forEach((model: any) => {
          graphModule.value.removeItem(model);
        });
      }
    });

    isNeedPreventDefault = true;
  }

  // 是否需要组织浏览器默认事件
  if (isNeedPreventDefault) {
    e.preventDefault();
    return false;
  }
};

let _beforeunloadHandler = (event) => {
  if (undoStackLen.value) {
    event.preventDefault();
    // Chrome requires returnValue to be set.
    event.returnValue = "您在页面编辑了未保存，是否确认离开";
    return "您在页面编辑了未保存，是否确认离开";
  }
};
//当前参数是用来控制，初始化页面时，改变布局造成入栈情况
const initStatus = ref(true);
const bindUndoRedo = () => {
  const graph = graphModule.value;
  graph.on("stackchange", (evt: any) => {
    if (!initStatus.value) {
      const { undoStack, redoStack } = evt;
      undoStackLen.value = undoStack.length;
      redoStackLen.value = redoStack.length;
    } else {
      initStatus.value = false;
    }
  });
};
const handleUndo = () => {
  const graph = graphModule.value;
  const undoStack = graph.getUndoStack();
  if (!undoStack || undoStack.length === 0) {
    return;
  }
  const currentData = undoStack.pop();
  if (currentData) {
    const { action } = currentData;
    graph.pushStack(action, clone(currentData.data), "redo");
    let data = currentData.data.before;
    if (action === "add") {
      data = currentData.data.after;
    }
    if (!data) return;
    switch (action) {
      case "visible": {
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            const item = graph.findById(model.id);
            if (model.visible) {
              graph.showItem(item, false);
            } else {
              graph.hideItem(item, false);
            }
          });
        });
        break;
      }
      case "render":
      case "update":
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            const item = graph.findById(model.id);
            delete model.id;
            graph.updateItem(item, model, false);
            if (item.getType() === "combo") graph.updateCombo(item as ICombo);
          });
        });
        break;
      case "changedata":
        graph.changeData(data, false);
        break;
      case "delete": {
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            const itemType = model.itemType;
            delete model.itemType;
            graph.addItem(itemType, model, false);
          });
        });
        break;
      }
      case "add":
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            graph.removeItem(model.id, false);
          });
        });
        break;
      case "updateComboTree":
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            graph.updateComboTree(model.id, model.parentId, false);
          });
        });
        break;
      case "createCombo":
        // eslint-disable-next-line no-case-declarations
        const afterCombos = currentData.data.after.combos;
        // eslint-disable-next-line no-case-declarations
        const createdCombo = afterCombos[afterCombos.length - 1];
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            graph.updateComboTree(model.id, model.parentId, false);
          });
        });
        graph.removeItem(createdCombo.id, false);
        break;
      case "uncombo":
        // eslint-disable-next-line no-case-declarations
        const targetCombo = data.combos[data.combos.length - 1];
        // eslint-disable-next-line no-case-declarations
        const childrenIds = data.nodes
          .concat(data.combos)
          .map((child) => child.id)
          .filter((id) => id !== targetCombo.id);
        graph.createCombo(targetCombo, childrenIds, false);
        break;
      case "layout":
        graph.updateLayout(data, undefined, undefined, false);
        break;
      default:
    }
  }
};
const handleRedo = () => {
  const graph = graphModule.value;
  const redoStack = graph.getRedoStack();

  if (!redoStack || redoStack.length === 0) {
    return;
  }

  const currentData = redoStack.pop();
  if (currentData) {
    const { action } = currentData;
    let data = currentData.data.after;
    graph.pushStack(action, clone(currentData.data));
    if (action === "delete") {
      data = currentData.data.before;
    }

    if (!data) return;

    switch (action) {
      case "visible": {
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            const item = graph.findById(model.id);
            if (model.visible) {
              graph.showItem(item, false);
            } else {
              graph.hideItem(item, false);
            }
          });
        });
        break;
      }
      case "render":
      case "update":
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            const item = graph.findById(model.id);
            delete model.id;
            graph.updateItem(item, model, false);
            if (item.getType() === "combo") graph.updateCombo(item as ICombo);
          });
        });
        break;
      case "changedata":
        graph.changeData(data, false);
        break;
      case "delete":
        if (data.edges) {
          data.edges.forEach((model) => {
            graph.removeItem(model.id, false);
          });
        }
        if (data.nodes) {
          data.nodes.forEach((model) => {
            graph.removeItem(model.id, false);
          });
        }
        if (data.combos) {
          data.combos.forEach((model) => {
            graph.removeItem(model.id, false);
          });
        }
        break;
      case "add": {
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            const itemType = model.itemType;
            delete model.itemType;
            graph.addItem(itemType, model, false);
          });
        });
        break;
      }
      case "updateComboTree":
        Object.keys(data).forEach((key) => {
          const array = data[key];
          if (!array) return;
          array.forEach((model) => {
            graph.updateComboTree(model.id, model.parentId, false);
          });
        });
        break;
      case "createCombo":
        // eslint-disable-next-line no-case-declarations
        const createdCombo = data.combos[data.combos.length - 1];
        graph.createCombo(
          createdCombo,
          createdCombo.children.map((child) => child.id),
          false
        );
        break;
      case "uncombo":
        // eslint-disable-next-line no-case-declarations
        const beforeCombos = currentData.data.before.combos;
        // eslint-disable-next-line no-case-declarations
        const targertCombo = beforeCombos[beforeCombos.length - 1];
        graph.uncombo(targertCombo.id, false);
        break;
      case "layout":
        graph.updateLayout(data, undefined, undefined, false);
        break;
      default:
    }
  }
};
const zoomOut = () => {
  const graph = graphModule.value;
  const currentZoom = graph.getZoom();
  const ratioOut = 1 / (1 - 0.05 * 0.2);
  const maxZoom = graph.get("maxZoom");
  if (ratioOut * currentZoom > maxZoom) {
    return;
  }
  graph.zoomTo(currentZoom * ratioOut);
};
const zoomIn = () => {
  const graph = graphModule.value;
  const currentZoom = graph.getZoom();
  const ratioIn = 1 - 0.05 * 0.2;
  const minZoom = graph.get("minZoom");
  if (ratioIn * currentZoom < minZoom) {
    return;
  }
  graph.zoomTo(currentZoom * ratioIn);
};
const handleSearchIcon = () => {
  let findNode = graphModule.value.findAll("node", (node) => {
    return node.get("model").label.includes(nodeName.value);
  });
  let findLine = graphModule.value.findAll("edge", (edge) => {
    return edge.get("model").label.includes(nodeName.value);
  });
  if (findNode?.length || findLine?.length) {
    setAllStateDark();
    findNode.forEach((node) => {
      graphModule.value.setItemState(node, "highlight", true);
    });
    findLine.forEach((edge) => {
      graphModule.value.setItemState(edge, "highlight", true);
    });
  } else {
    setAllStateDark();
    ElMessage.warning("无搜索结果");
  }
};

const handleClearIcon = () => {
  clearAllStats();
};

const layoutActive = ref("gForce");
const popoverRef = ref();
const layoutList = [
  {
    name: "力导向图",
    type: "gForce",
  },
  {
    name: "层次图",
    type: "dagre",
  },
  {
    name: "层次图-水平方向",
    type: "dagre-lr",
  },
  {
    name: "环形图",
    type: "circular",
  },
  {
    name: "网格",
    type: "grid",
  },
  {
    name: "同心圆",
    type: "concentric",
  },
  {
    name: "辐射",
    type: "radial",
  },
];
const popoverVisible = ref(false);
const popoverInfoVisible = ref(false);
const buttonRef = ref();
const onClickOutside = () => {
  unref(popoverRef).popperRef?.delayHide?.();
  // layoutActive.value = true;
};

const changeLayout = (type) => {
  layoutActive.value = type;
  emitter.emit("graphLayoutActive", type);
  popoverVisible.value = false;
};
const handleSave = async () => {
  let graphData = graphModule.value.save();

  let graphDeelData = deelSubmitData(graphData, layoutActive.value);
  let res = await graphSaveApi(graphDeelData);
  if (res.code === 200) {
    let renderData = res.data as any;
    deelNode(renderData);
    renderData?.edges && G6.Util.processParallelEdges(renderData.edges);
    graphModule.value.data(res.data);
    graphModule.value.render();
    ElMessage.success("保存成功");
  }
};
const handleExport = async () => {
  let res = await downloadDesignApi();
  exportFile.getExcel(res.data, "本体建模");
};
onUnmounted(() => {
  emitter.off("graphInit");
  emitter.off("changeBarLayout");
  window.removeEventListener("beforeunload", _beforeunloadHandler);
  window.removeEventListener("keydown", linsenKeyboard);
});

onBeforeRouteLeave((to, form, next) => {
  if (undoStackLen.value) {
    // *** 此处必须要加延迟执行、不加的话点击浏览器按钮时会存在问题，弹框一闪而过，
    // 页面没有变，但是地址已经变了
    setTimeout(() => {
      ElMessageBox({
        title: "提示",
        message: `
            <div style="min-height: 50px; line-height: 22px;">
            <i class="el-icon-warning" style="color: rgb(90, 164, 255); font-size: 20px; vertical-align: middle;"></i>
            <span style="color: rgb(48, 49, 51);">您在画布中编辑了未保存，是否确认离开</span>
          </div>
          `,
        showCancelButton: true,
        dangerouslyUseHTMLString: true,
        // type: 'warning',
        confirmButtonText: "确定",
        cancelButtonText: "取消",
      })
        .then(async () => {
          // 正常跳转
          next();
        })
        .catch(() => {
          // 如果取消跳转地址栏会变化，这时保持地址栏不变
          next(false);
        });
    }, 100);
  } else {
    next();
  }
});
</script>

<style lang="scss" scoped>
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  height: 50px;
  padding: 0 15px;

  .el-input {
    width: 250px;
  }
}

.toolbar-btns-list {
  align-items: center;
  height: 100%;
  font-size: 0;
}

.tip-div {
  display: flex;
  flex-wrap: wrap;
  width: 100%;

  // height: 200px;
  padding: 20px;

  .item {
    width: 50%;
    margin-top: 10px;
    font-size: 15px;

    // font-weight: bold;

    span:nth-child(1) {
      display: inline-block;
      width: 100px;

      // margin-right: 30px;
    }
  }
}

.toolbar-btns {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  text-align: center;
  cursor: pointer;

  .iconfont {
    font-size: 20px;
    color: #575d6c;
  }

  &.disabled .iconfont {
    @apply text-gray-400 cursor-no-drop;

    pointer-events: none;

    &:hover {
      @apply text-gray-400;
    }
  }

  &.active {
    @apply bg-gray-300 text-[#2c51d7];
  }

  &:hover {
    @apply text-[#2c51d7];
  }

  &.toolbar-autoZoom {
    .icon {
      background: url("./images/graph-fullscreen.svg") no-repeat;
    }
  }

  &.toolbar-expand {
    margin-bottom: 0;

    .icon {
      background: url("./images/left-arrow.svg") no-repeat;
      transition: all 0.3s;
      transform: rotate(180deg);
    }
  }

  &.toolbar-collapse {
    .icon {
      transform: rotate(0deg);
    }
  }

  &.toolbar-layout {
    position: relative;

    // &::before {
    //   position: absolute;
    //   right: -12px;
    //   width: 25px;
    //   height: 25px;
    //   content: "";
    //   background: url("../images/icon-list-down.svg") no-repeat;
    // }
  }
}

.toolbar-btns,
.layout-item {
  .icon {
    display: inline-block;
    width: 30px;
    height: 30px;
  }

  svg {
    width: 20px;
    height: 20px;
  }

  &.gForce {
    .icon {
      background: url("./images/graph-graphView.svg") no-repeat;
    }
  }

  &.dagre {
    .icon {
      background: url("./images/graph-treeView.svg") no-repeat;
    }
  }

  &.dagre-lr {
    .icon {
      background: url("./images/graph-treeView.svg") no-repeat;
      transform: rotate(-90deg);
    }
  }

  &.circular {
    .icon {
      background: url("./images/graph-sphereView.svg") no-repeat;
    }
  }

  &.grid {
    .icon {
      background: url("./images/graph-gridView.svg") no-repeat;
    }
  }

  &.concentric {
    .icon {
      background: url("./images/graph-Radial.svg") no-repeat;
    }
  }

  &.radial {
    .icon {
      background: url("./images/graph-relations.svg") no-repeat;
    }
  }
}

.layout-list {
  .layout-item {
    height: 35px;
    margin-bottom: 5px;
    font-size: 14px;
    line-height: 35px;
    color: #000;
    white-space: nowrap;
    cursor: pointer;

    &.grid {
      display: block;
    }

    .icon {
      margin-right: 5px;
      vertical-align: middle;
    }

    .text {
      display: inline-block;
      vertical-align: middle;
    }
  }
}

.toolbar-tooltip {
  justify-items: end;
  font-size: 12px;
}
</style>
